#include "HashExtensible.h"
#include "../../ControladorIDsLibres/src/ControladorIDsLibres.c"


HashExtensible* CrearHashExtensible (TamBucket tamanioBloque,char* nombre)
{
	HashExtensible* hash = (HashExtensible*) malloc(sizeof(HashExtensible));

	//Devuelve un numero 512*2^n mas cercano a 'tamanioBloque'

	div_t aux = div(tamanioBloque,512);

	if (aux.quot !=0){

		double potencia = log(aux.quot)/log(2);

		aux.quot = potencia;
	}

	int tamanioBloqueAux =  512 * pow(2,aux.quot);

	int dif = tamanioBloque - tamanioBloqueAux;

	if (dif >= (tamanioBloqueAux/2))
		tamanioBloqueAux *= 2;

	tamanioBloque = tamanioBloqueAux;

	int long_Nombre = strlen(nombre);
	//rutaTabla
	char extensionTabla[] = "-Tabla";
	char * rutaTabla = (char*) malloc(sizeof(char)*(long_Nombre + strlen(extensionTabla)+1));

	rutaTabla[0] = '\0';

	int posDot = strcspn(nombre, "."); //posicion del '.', que refiere a la extension del archivo
	strncat (rutaTabla, nombre, posDot);
	strcat (rutaTabla, extensionTabla);
	strcat (rutaTabla, nombre + posDot);

	//Existe rutaTabla y rutaBloques, o no existe ninguno
	hash->archTabla = fopen(rutaTabla,"r+");
	hash->archBuckets = fopen(nombre,"r+");
	if ((hash->archTabla == NULL && hash->archBuckets == NULL) || (hash->archTabla != NULL && hash->archBuckets != NULL))
	{
		if (hash->archTabla == NULL)
		{
			//No se pudo abrir el archivo, se crea.
			hash->archTabla  = fopen(rutaTabla,"w+");
		}

		if (hash->archBuckets == NULL)
		{
			//No se pudo abrir el archivo, se crea.
			hash->archBuckets  = fopen(nombre,"w+");
		}

		// Creo o levanto la tabla, dependiendo de su existencia.
		hash->Tabla = LevantarTabla(hash->archTabla, tamanioBloque);

		//inicializo el controlador de nodos libres

		char extensionBloquesLibres[] = "-BloquesLibres";
		char * rutaBloquesLibres = (char*) malloc(sizeof(char)*(long_Nombre + strlen(extensionBloquesLibres)+1));

		rutaBloquesLibres[0] = '\0';
		strncat (rutaBloquesLibres, nombre, posDot);
		strcat (rutaBloquesLibres, extensionBloquesLibres);
		strcat (rutaBloquesLibres, nombre + posDot);


		hash->controladorIDsLibres = (ControladorIDsLibres*) malloc(sizeof(ControladorIDsLibres));

		ControladorIDsLibres_Iniciar(hash->controladorIDsLibres, rutaBloquesLibres, hash->Tabla->tamanioBucket);

		free(rutaTabla);
		free(rutaBloquesLibres);

		hash->esTemporal = FALSE;
		hash->nombreArch = (char*) malloc (strlen(nombre) + 1);
		strcpy (hash->nombreArch, nombre);

		return hash;
	}
	else
	{
		free (hash);
		return NULL;
	}
}

void CerrarHashExtensible (HashExtensible* hash)
{
	if (hash != NULL)
	{

		if (hash->Tabla != NULL)
		{
			free(hash->Tabla->Buckets);
			free(hash->Tabla->BloquesTabla);
			free(hash->Tabla);
		}

		ControladorIDsLibres_cerrar(hash->controladorIDsLibres);
		free(hash->controladorIDsLibres);

		fclose(hash->archTabla);
		fclose(hash->archBuckets);

		if (hash->esTemporal == TRUE)
		{
			int long_Nombre = strlen(hash->nombreArch);
			//Ruta Tabla
			char extensionTabla[] = "-Tabla";
			char * rutaTabla = (char*) malloc(sizeof(char)*(long_Nombre + strlen(extensionTabla)+1));

			rutaTabla[0] = '\0';

			int posDot = strcspn(hash->nombreArch, "."); //posicion del '.', que refiere a la extension del archivo
			strncat (rutaTabla, hash->nombreArch, posDot);
			strcat (rutaTabla, extensionTabla);
			strcat (rutaTabla, hash->nombreArch + posDot);
			remove(rutaTabla); //Borro el archivo de la tabla
			free (rutaTabla);

			//Ruta Bloques Libres
			char extensionBloquesLibres[] = "-BloquesLibres";
			char * rutaBloquesLibres = (char*) malloc(sizeof(char)*(long_Nombre + strlen(extensionBloquesLibres)+1));

			rutaBloquesLibres[0] = '\0';
			strncat (rutaBloquesLibres, hash->nombreArch, posDot);
			strcat (rutaBloquesLibres, extensionBloquesLibres);
			strcat (rutaBloquesLibres, hash->nombreArch + posDot);
			remove(rutaBloquesLibres);//Borro el archivo de los bloques libres
			free (rutaBloquesLibres);

			remove(hash->nombreArch);//Borro el archivo de Buckets
		}

		free(hash->nombreArch);

		free(hash);
	}
}


Bucket* SplitBloque (HashExtensible* hash,Bucket* bucket, unsigned int indice)
{
	enum TBOOL cambioMetaData = FALSE;

	if (bucket->tamDispersion == hash->Tabla->tamanioTabla)
	{
		DuplicarTabla(hash->Tabla);
		cambioMetaData = TRUE;
	}

	Bucket* nuevoBucket = CrearBucket(hash->Tabla->tamanioBucket);

	bucket->tamDispersion = bucket->tamDispersion * 2;
	nuevoBucket->tamDispersion = bucket->tamDispersion;

	if(ControladorIDsLibres_tieneIDLibre(hash->controladorIDsLibres)) //se usará un bloque libre
	{
		unsigned int nroBloqueLibre = ControladorIDsLibres_getIDLibre(hash->controladorIDsLibres);
		nuevoBucket->nroBucket = nroBloqueLibre;
	}
	else //No hay bloques libres para usar
	{
		nuevoBucket->nroBucket = hash->Tabla->cantBuckets;
		hash->Tabla->cantBuckets++;
		cambioMetaData = TRUE;
	}

	//Actualiza la Tabla de Hashing
	GuardarBloqueEnTabla(hash->Tabla, nuevoBucket->nroBucket, nuevoBucket->tamDispersion, indice);
	//Escribe en el archivo
	GuardarTabla(hash->archTabla, hash->Tabla);

	if (cambioMetaData == TRUE)
		GuardarMetaData(hash->archTabla, hash->Tabla);

	DistribuirClaves(*hash->Tabla, bucket, nuevoBucket); //distribuye las claves entre los 2 buckets

	return nuevoBucket;
}

int InsertarElemento(HashExtensible* hash, TClave clave, void* elemento, unsigned int longElemento)
{
	if (longElemento > hash->Tabla->tamanioBucket) return ERROR;

	Bucket* bucket;
	unsigned int indice;

	//Chequeo que la Tabla no este vacía
	if (hash->Tabla->tamanioTabla == 0)
	{
		bucket = CrearBucket(hash->Tabla->tamanioBucket);
		if (hash->Tabla->cantBuckets == 0) //si es la primera vez que se usa la tabla
		{
			hash->Tabla->cantBuckets++;
			bucket->nroBucket = 0;
		}
		else
		{ // ver si tiene iDs Libres
			bucket->nroBucket = ControladorIDsLibres_getIDLibre(hash->controladorIDsLibres);
		}

		hash->Tabla->tamanioTabla++;
		bucket->tamDispersion = hash->Tabla->tamanioTabla;
		indice = IndiceEnTabla (clave, hash->Tabla->tamanioTabla);

		hash->Tabla->Buckets = (unsigned int *) malloc(sizeof(unsigned int) * hash->Tabla->tamanioTabla);
		hash->Tabla->Buckets[0] = bucket->nroBucket;

		int cantBloquesTabla =  getCantBloquesTabla(*hash->Tabla);
		hash->Tabla->BloquesTabla = (enum TBOOL *) malloc (sizeof(enum TBOOL) * cantBloquesTabla);
		hash->Tabla->BloquesTabla[0] = TRUE;

		int guardado = GuardarMetaData(hash->archTabla, hash->Tabla);
		if (guardado == ERROR) return ERROR;

		guardado = GuardarTabla (hash->archTabla, hash->Tabla);
		if (guardado == ERROR) return ERROR;
	}
	else
	{
		// Busco el indice de la tabla por la clave
		indice = IndiceEnTabla (clave, hash->Tabla->tamanioTabla);
		//Busco el numero de bloque en la posicion de la tabla
		unsigned int numBloque = hash->Tabla->Buckets[indice];
		// Levanto el bloque del archivo
		bucket = (Bucket*) LevantarBucket(hash->archBuckets, numBloque, hash->Tabla->tamanioBucket);
	}

	//Se valida unicidad.
	if(BuscarElementoBucket(bucket,clave)== OK)
	{
		LiberarBucket(bucket);
		return ERROR;
	}

	if( VerificarEspacioLibreEnBucket(*bucket, clave, longElemento) == OK)
	{
		InsertarElementoBucket(bucket,clave,elemento,longElemento);
		int guardo = GuardarBucket(hash->archBuckets,bucket);
		LiberarBucket(bucket);
		return guardo;
	}

	return sinEspacioEnBucket (hash, bucket, clave, elemento, longElemento, indice, TRUE);
}

int sinEspacioEnBucket (HashExtensible* hash, Bucket* bucket, TClave clave, void* elemento, unsigned short int longElemento, unsigned int indice, enum TBOOL esInsercion)
{
	Bucket * companiero = SplitBloque(hash, bucket, indice);
	GuardarBucket(hash->archBuckets, companiero);
	GuardarBucket(hash->archBuckets, bucket);
	LiberarBucket(companiero);
	LiberarBucket(bucket);
	if (esInsercion == TRUE)
		return InsertarElemento(hash, clave, elemento, longElemento);
	else
		return ModificarElemento(hash, clave, elemento, longElemento);
}


void DistribuirClaves (TablaHash tabla, Bucket* bucket, Bucket* nuevoBucket)
{
	Elemento* elementosBuket = (Elemento*) malloc (sizeof(Elemento) * bucket->cantElementos);
	TLongElem* longElementos = (TLongElem*) malloc (sizeof(TLongElem) * bucket->cantElementos);
	unsigned int cantidadElementos = bucket->cantElementos;
	int i;
	//copio las longitudes al vector auxiliar de longitudes.
	for (i=0;i<cantidadElementos;i++)
	{
		longElementos[i] = bucket->longitudElemento[i];
		bucket->bytesLibres += sizeof(TLongElem);
	}
	//libero las longitudes del bucket.
	free(bucket->longitudElemento);
	bucket->longitudElemento = NULL;
	//copio los elementos al vector auxiliar de elementos
	int long_clave = 0;
	for(i=0;i<cantidadElementos;i++)
	{
		elementosBuket[i].dato = (void*) malloc (longElementos[i]);
		memcpy(elementosBuket[i].dato, bucket->elementos[i].dato, longElementos[i]);
		long_clave = (strlen(bucket->elementos[i].clave)+1);
		elementosBuket[i].clave = (TClave) malloc(sizeof(char)*long_clave);
		strcpy(elementosBuket[i].clave, bucket->elementos[i].clave);
		bucket->bytesLibres += (sizeof (char)*long_clave) + longElementos[i];
	}

	liberarElementos(bucket->elementos,cantidadElementos);
	bucket->cantElementos = 0;

	//Distribuyo los elementos.
	unsigned int indice=0,cantElemBq=0,cantElemNuevoBq = 0;

	for (i=0; i<cantidadElementos; i++)
	{
		indice = IndiceEnTabla(elementosBuket[i].clave, tabla.tamanioTabla);
		if (tabla.Buckets[indice] != bucket->nroBucket)
		{
			cantElemNuevoBq++;
			if(nuevoBucket->longitudElemento == NULL)
			{
				nuevoBucket->longitudElemento = (TLongElem*) malloc (sizeof(TLongElem)*cantElemNuevoBq);
			}
			else
			{
				nuevoBucket->longitudElemento = (TLongElem*) realloc (nuevoBucket->longitudElemento , sizeof(TLongElem)*cantElemNuevoBq);
			}
			nuevoBucket->longitudElemento[cantElemNuevoBq -1] = longElementos[i];

			if(nuevoBucket->elementos == NULL)
			{
				nuevoBucket->elementos = (Elemento*) malloc (sizeof(Elemento)*cantElemNuevoBq);
			}
			else
			{
				nuevoBucket->elementos = (Elemento*) realloc (nuevoBucket->elementos, sizeof(Elemento)*cantElemNuevoBq);
			}
			long_clave = strlen(elementosBuket[i].clave)+1;
			nuevoBucket->elementos[cantElemNuevoBq-1].clave = (TClave) malloc(sizeof(char)*long_clave);
			strcpy(nuevoBucket->elementos[cantElemNuevoBq-1].clave, elementosBuket[i].clave);
			nuevoBucket->elementos[cantElemNuevoBq-1].dato = (void*) malloc (longElementos[i]);
			memcpy(nuevoBucket->elementos[cantElemNuevoBq-1].dato,elementosBuket[i].dato,longElementos[i]);

			nuevoBucket->bytesLibres -= (sizeof(TLongElem)) + sizeof (TClave) + longElementos[i];
		}
		else
		{
			cantElemBq++;
			if(bucket->longitudElemento == NULL)
			{
				bucket->longitudElemento = (TLongElem*) malloc (sizeof(TLongElem)*cantElemBq);
			}
			else
			{
				bucket->longitudElemento = (TLongElem*) realloc (bucket->longitudElemento , sizeof(TLongElem)*cantElemBq);
			}
			bucket->longitudElemento[cantElemBq -1] = longElementos[i];

			if(bucket->elementos == NULL)
			{
				bucket->elementos = (Elemento*) malloc (sizeof(Elemento)*cantElemBq);
			}
			else
			{
				bucket->elementos = (Elemento*) realloc (bucket->elementos, sizeof(Elemento)*cantElemBq);
			}
			long_clave = strlen(elementosBuket[i].clave) + 1;
			bucket->elementos[cantElemBq-1].clave = (TClave) malloc(sizeof(char)*long_clave);
			strcpy(bucket->elementos[cantElemBq-1].clave, elementosBuket[i].clave);
			bucket->elementos[cantElemBq-1].dato = (void*) malloc (longElementos[i]);
			memcpy(bucket->elementos[cantElemBq-1].dato,elementosBuket[i].dato,longElementos[i]);

			bucket->bytesLibres -= (sizeof(TLongElem)) + sizeof (TClave) + longElementos[i];
		}
	}
	nuevoBucket->cantElementos = cantElemNuevoBq;
	bucket->cantElementos = cantElemBq;

	liberarElementos(elementosBuket,cantidadElementos);
	free(elementosBuket);
	free(longElementos);
}

void* ObtenerElemento (HashExtensible* hash, TClave clave)
{
	if(hash->Tabla->tamanioTabla > 0)
	{
		unsigned int indice = IndiceEnTabla(clave,hash->Tabla->tamanioTabla);

		unsigned int* longElem = (unsigned int*) malloc (sizeof(unsigned int));

		Bucket* bucketABuscar = (Bucket*) LevantarBucket(hash->archBuckets, hash->Tabla->Buckets[indice], hash->Tabla->tamanioBucket);

		void* buscado = ObtenerElementoBucket(*bucketABuscar, clave, longElem);

		LiberarBucket(bucketABuscar);

		free(longElem);

		return buscado;
	}
	return NULL;
}

int EliminarElemento (HashExtensible* hash, TClave clave)
{
	if(hash->Tabla->tamanioTabla > 0)
	{
		unsigned int indice = IndiceEnTabla(clave,hash->Tabla->tamanioTabla);

		unsigned int nroBucket = hash->Tabla->Buckets[indice];

		Bucket* bucket = LevantarBucket(hash->archBuckets, nroBucket, hash->Tabla->tamanioBucket);

		int resultado = EliminarElementoBucket(bucket,clave);

		/*Si no se encontro el elemento, devuelve ERROR*/
		if (resultado  == ERROR)
		{
			LiberarBucket(bucket);
			return ERROR;
		}

		//Se guarda el bucket modificado en el archivo.
		GuardarBucket(hash->archBuckets,bucket);

		if(bucket->cantElementos==0)
		{ //si quedo vacio me fijo si puedo desreferenciarlo
			int dispersion = (int)(bucket->tamDispersion)/2;

			int bucketSuperior = hash->Tabla->Buckets[(indice+dispersion)%(hash->Tabla->tamanioTabla)];
			int bucketInferior = hash->Tabla->Buckets[(indice-dispersion)%(hash->Tabla->tamanioTabla)];
			if (bucketSuperior == bucketInferior)
			{
				int i;
				for(i=0; i<hash->Tabla->tamanioTabla; i++)
				{
					if(nroBucket == hash->Tabla->Buckets[i]) //reemplazo cada vez que esta
					{
						hash->Tabla->Buckets[i]= bucketSuperior;

						div_t bloque;
						bloque = div((i * sizeof(unsigned int)), hash->Tabla->tamanioBucket); // DIV
						hash->Tabla->BloquesTabla[bloque.quot] = TRUE;
					}
				}

				ReducirTabla(hash->Tabla, hash->archTabla); //me  fijo si puedo reducir
				GuardarTabla(hash->archTabla, hash->Tabla);

				ControladorIDsLibres_addIDLibre(hash->controladorIDsLibres, nroBucket);

				//Actualizo el tamaño de dispersion del bloque.
				Bucket* bucketAux = (Bucket*) LevantarBucket(hash->archBuckets, bucketSuperior, hash->Tabla->tamanioBucket);
				bucketAux->tamDispersion /= 2;
				GuardarBucket(hash->archBuckets, bucketAux);
				LiberarBucket(bucketAux);
			}
		}

		LiberarBucket(bucket);
		return OK;
	}
	return ERROR;
}

int ModificarElemento (HashExtensible* hash, TClave clave, void* elemento, unsigned short int longElemento)
{
	if(hash->Tabla->tamanioTabla > 0)
	{
		unsigned int longViejo;// = (unsigned int*) malloc (sizeof(unsigned int)); //TODO por q no declaro directamente como unsigned int?

		unsigned int indice = IndiceEnTabla(clave,hash->Tabla->tamanioTabla);
		Bucket* bucket = (Bucket*) LevantarBucket(hash->archBuckets, hash->Tabla->Buckets[indice], hash->Tabla->tamanioBucket);
		void * elemViejo = ObtenerElementoBucket (*bucket, clave, &longViejo);

		if(elemViejo == NULL)
		{
			//free(longViejo);
			return ERROR;
		}

		free(elemViejo);

		int espacio = bucket->bytesLibres+longViejo-longElemento;

		//free(longViejo);

		if (espacio < 0) //Si no hay espacio para la modificacion
		{
			return sinEspacioEnBucket(hash, bucket, clave, elemento, longElemento, indice, FALSE); //hace Split
		}
		else
		{
			bucket->bytesLibres +=longViejo;
			int modif = ModificarElementoBucket(bucket, clave, elemento, longElemento);
			GuardarBucket(hash->archBuckets, bucket);
			LiberarBucket(bucket);
			return modif;
		}
	}
	return ERROR;
}


void visualizar(HashExtensible* hash,int full, FILE* salida)
{
	if (hash->Tabla->cantBuckets==0)
	{
		fprintf(salida,"El Hash esta vacio.\n");
	}
	else
	{
		visualizarTabla(hash->Tabla,salida);

		fprintf(salida,"\nArchivo:\n");

		unsigned int i = 0;

		Bucket* bucket = LevantarBucket(hash->archBuckets, i, hash->Tabla->tamanioBucket);

		while(bucket!=NULL)
		{
			visualizarBucket(bucket,salida);
			LiberarBucket(bucket);
			i++;
			bucket = LevantarBucket(hash->archBuckets, i, hash->Tabla->tamanioBucket);
		}
		unsigned int cantidadIDsLibres;
		unsigned int* IDsLibres  = ControladorIDsLibres_getAllLibres(hash->controladorIDsLibres,&cantidadIDsLibres);

		fprintf(salida,"Bloques libres (%i): ",cantidadIDsLibres);
		if(cantidadIDsLibres>0)
		{
				for (i=0; i < cantidadIDsLibres-1; i++)
				{
					fprintf(salida,"%i, ", IDsLibres[i]);
				}
				fprintf(salida,"%i", IDsLibres[i]);
		}
		free(IDsLibres);

		if(full == TRUE)
		{
			fprintf(salida,"\n\nElementos:\n");
			i=0;
			bucket = LevantarBucket(hash->archBuckets, i, hash->Tabla->tamanioBucket);
			while(bucket!=NULL)
			{
				visualizarElementosBucket(bucket,salida);
				LiberarBucket(bucket);
				i++;
				bucket = LevantarBucket(hash->archBuckets, i, hash->Tabla->tamanioBucket);
			}
		}

		fprintf(salida,"\n\n**************************************\n\n");

	}
}

/*ITERADOR*/

iteradorHash * CrearIteradorHash()
{
	iteradorHash * it = (iteradorHash *) malloc(sizeof (iteradorHash));
	it->numBucket = -1;
	it->indiceElemBucket = 0;
	it->bucketActual = NULL;
	return it;
}

void * getSiguienteElem (HashExtensible * hash, iteradorHash * it, TClave* claveDevuelta)
{
	if (it->numBucket == -1)
	{
		it->numBucket++;
		it->indiceElemBucket = 0;
		it->bucketActual = LevantarBucket(hash->archBuckets, it->numBucket,hash->Tabla->tamanioBucket);
	}
	else
		it->indiceElemBucket++;

	void * elem = NULL;
	elem = ObtenerElementoByIndice(it->bucketActual, it->indiceElemBucket, claveDevuelta);
	if (elem != NULL)
		return elem;

	//se acabaron los elementos del bucket, se levanta un bucket nuevo
	it->indiceElemBucket = 0;

	while ((it->bucketActual != NULL) && (elem == NULL))
	{
		LiberarBucket(it->bucketActual);
		it->numBucket++;
		it->bucketActual = LevantarBucket(hash->archBuckets, it->numBucket, hash->Tabla->tamanioBucket);
		if(it->bucketActual != NULL)
		{
			elem = ObtenerElementoByIndice(it->bucketActual, it->indiceElemBucket, claveDevuelta);
			if (elem != NULL)
				return elem;
		}

	}
	it->numBucket = -1;
	return NULL;
}

void EliminarIteradorHash (iteradorHash * it)
{
	if (it->bucketActual != NULL)
		LiberarBucket(it->bucketActual);
	free (it);
	it = NULL;
}

/*FIN ITERADOR*/

/*Es Temporal*/

enum TBOOL getEsTemporal (HashExtensible * hash)
{
	return hash->esTemporal;
}

void setEsTemporal (HashExtensible * hash, enum TBOOL esTemporal)
{
	hash->esTemporal = esTemporal;
}

/*FIN Es Temporal*/


